package Game;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class GameModel {
	private enum ModeType {
		Increasing, Stable, Decreasing
	}
	
	private static final double DELTA_TIME_RATIO = 1000.0 / 60.0;     //makes application run at a base FPS of 60
	
	private long prevLoopTime;
	
	private double scoreCounter = 0;
	private int currentScore = 0;
	private int bestScore = 0;
	private ModeType mode;
	
	private boolean isOver;
	Player player;
	
	private Random random;
	private double counter;
	
	private Set<Platform> platforms;	//it'd probably be more efficient to have a pool of these or something
	private Set<Platform> platformsToKill;	//Since we don't have a pool of these, kill platforms at the start of the game update in this list
	
	private double intensity;	//intensity controls the difficulty of the game at the moment
	private double speed;	//the speed at which the player is running (actually how fast the platforms move by)
	
	private double horzDistance;	//the current horizontal distance between generating platforms
	private double vertDistance;	//the current vertical distance between generating platfoms, you can make the platforms start to descend by making this negative
	private double platformWidth;	//the current platform width
		
	private double horzDistanceMinRange;		//the lower range when randomly jittering the horizontal distance
	private double horzDistanceMaxRange;		//the upper range when randomly jittering the horizontal distance
	
	private double vertDistanceMinRange;		//the lower range when randomly jittering the vertical distance
	private double vertDistanceMaxRange;		//the upper range when randomly jittering the vertical distance
	
	private double platformWidthMinRange;		//the lower range when randomly jittering the platform width
	private double platformWidthMaxRange;		//the upper range when randomly jittering the platform width
	
	private double distanceToSpawnNext;			//how much distance needs to go by before spawning the next platform
	private double lastPlatformHeight;			//the y coordinate of the last spawned platform
	
	public int randomInt(int range) {
		return random.nextInt(range);
	}
	
	//returns a random double within the range min and max, this kind of thing normally goes into some util class
	public double randomDouble(double min, double max) {
		return random.nextDouble() * (max - min) + min;
	}
	
	public int getBestScore() {
		return bestScore;
	}
	
	public GameModel() {
		prevLoopTime = 0;
		
		isOver = false;
		
		scoreCounter = 0;
		bestScore = 0;
		currentScore = 0;
		
		platforms = new HashSet<Platform>();
		platformsToKill = new HashSet<Platform>();
		
		createInit();

		random = new Random();
	}

	private void createInit() {
		intensity = 0;
		speed = 2.5;
		
		//create initial platform
		lastPlatformHeight = 200.0;
		counter = 0;
		platforms.add(new Platform(this, 400.0, new Position(0.0, lastPlatformHeight)));
		distanceToSpawnNext = 0.0;
		horzDistance = 10.0;
        vertDistance = -30.0;
        platformWidth = 100.0;
        mode = ModeType.Stable;
        
        horzDistance = 10.0 + (1.25 * speed);
		platformWidth = 100.0 + (20.0 * speed);
        
		player = new Player(this, new Position(100, 170));
	}
	
	public void playerJumped() {
		player.jump();
	}
	
	private void runAI(double dt) {	
		//create some platforms with our amazing AI algorithm
        //TODO: change these values based on intensity and make the intensity go up and down with some math function?
        //for now these are hardcoded
		
		if (scoreCounter > 5) {
	        currentScore++;
	        scoreCounter -= 5;
	        
	        if (currentScore > bestScore) {
				bestScore = currentScore;
			}
		}
		
		if (counter > 200) {
			counter = 0;
			
			if (mode == ModeType.Increasing) {
				intensity += 0.1;
				speed += intensity;
				
				if (intensity >= 1.0) {
					intensity = 0.0;
					mode = ModeType.Decreasing;
				}
			}
			else if (mode == ModeType.Decreasing) {
				intensity += 0.1;
				speed -= (intensity / 2);
				
				if (intensity >= 1.0) {
					intensity = 0.0;
					mode = ModeType.Decreasing;
				}
				
			}
			else {
				intensity += 0.1;
				
				if (intensity >= 0.5) {
					intensity = 0.0;
					mode = ModeType.Increasing;
				}
			}
			
			if (speed > 7.5) {
				speed = 7.5;
			}
			else if (speed < 1.5) {
				speed = 1.5;
			}
			
			
			horzDistance = 10.0 + (1.25 * speed);
			platformWidth = 100.0 + (20.0 * speed);
		}
        
        //horzDistance = 10.0;
        vertDistance = 20.0;
        //platformWidth = 100.0;
        
        horzDistanceMinRange = 0.0;
        vertDistanceMinRange = 0.0;
        
        horzDistanceMaxRange = 0.0;
        vertDistanceMaxRange = 10.0;
        
        platformWidthMinRange = 0.0;
        platformWidthMaxRange = 0.0;
                
        distanceToSpawnNext -= speed * dt;
        
        //spawn the platform
        if(distanceToSpawnNext <= 0.0) {
        	int randNum = randomInt(100);
        	int higherProb = getIncreaseHeightChance();
        	
        	// Always have a base 30% for staying at the same height
        	if (randNum < 30) {
        		horzDistanceMinRange = (28.0 * speed) - 28;
        		horzDistanceMaxRange = (35.0 * speed) - 35;
        	}
        	// Calculate the chance of increasing the height depending on the current height.
        	else if (randNum < higherProb) {
        		horzDistanceMinRange = (23.0 * speed) - 23;
        		horzDistanceMaxRange = (26.0 * speed) - 26;
        		
        		lastPlatformHeight -= vertDistance + randomDouble(vertDistanceMinRange, vertDistanceMaxRange);
        	}
        	else {
        		horzDistanceMinRange = (31.0 * speed) - 31;
        		horzDistanceMaxRange = (34.0 * speed) - 34;
        		
        		lastPlatformHeight += vertDistance + randomDouble(vertDistanceMinRange, vertDistanceMaxRange);
        	}
        	
        	double widthRand = randomDouble(horzDistanceMinRange, horzDistanceMaxRange);
        	double thisWidth = platformWidth + randomDouble(platformWidthMinRange, platformWidthMaxRange);
        	
        	platforms.add(new Platform(this, thisWidth, new Position(widthRand + 400.0, lastPlatformHeight)));
        	
        	thisWidth += horzDistance + widthRand;
        	distanceToSpawnNext = thisWidth;
        }
	}
	
	private int getIncreaseHeightChance() {
		int baseChance = 30;
		int offset = (int)(35.0 * (1 + ((lastPlatformHeight - 200.0) / 150.0)));
		
		if (offset >= 70) {
			baseChance = 100;
		}
		else if (offset <= 0) {
			baseChance = 30;
		} 
		else {
			baseChance += offset;
		}
		
		return baseChance;
	}
	
	public int getScore() {
		return currentScore;
	}
	
	private void gameStep(double dt) {
		//kill platforms that are past the edge of the screen
        platforms.removeAll(platformsToKill);
        platformsToKill.clear();
        
        counter += dt;
        scoreCounter += dt;
        
        runAI(dt);
        
		for(Platform platform : platforms) {
			platform.gameLoop(dt);
		}
		
		player.gameLoop(dt);
		
		if (player.isDead()) {
			resetLevel();
			
			if (currentScore > bestScore) {
				bestScore = currentScore;
			}
		}
	}
	
	public void gameLoop() {
		double time = System.currentTimeMillis() - prevLoopTime;
		double deltaTime = time / DELTA_TIME_RATIO;
		
		prevLoopTime = System.currentTimeMillis();
		
		if(deltaTime > 7.5) {
			deltaTime = 7.5;
		}
		
		double remainStep = deltaTime;
		double timeStep = remainStep >= 1.0 ? 1.0 : remainStep;
		
		//fixing the time step
		while(remainStep > 0.0) {
			gameStep(timeStep);
			
			remainStep--;

			timeStep = remainStep >= 1 ? 1 : remainStep;
		}
	}
	
	public void resetLevel() {
		platforms = new HashSet<Platform>();
		platformsToKill = new HashSet<Platform>();
		
		intensity = 0;
		speed = 1.5;
		mode = ModeType.Stable;
		currentScore = 0;
		scoreCounter = 0;
		
		
		createInit();
		
		counter = 0;
	}
	
	public Position getPlayerPosition() {
		return player.getPosition();
	}
	
	public int getPlayerLength() {
		return player.getLength();
	}
	
	public void killPlatform(Platform platform) {
		platformsToKill.add(platform);
	}
	
	//so much for this being private
	public Set<Platform> getPlatforms() {
		return platforms;
	}
	
	public double getSpeed() {
		return speed;
	}
	
	public double getIntensity() {
		return intensity;
	}
	
	public Player getPlayer() {
		return player;
	}
	
	public boolean IsOver() {
		return isOver;
	}
	
}
